# coding=utf-8

from typing import Tuple, Optional, Iterable
import re
import lxml.etree


class _Assert:
    def __init__(self, expected: str, actual: str, negater: bool, failure_msg: str=None):
        """
        :param expected: 期望值
        :param actual: 实际值
        :param negater: 是否取反
        :param failure_msg: 错误信息 （暂未支持自定义错误信息）
        """
        self.expected = expected
        self.actual = actual
        self.failure_msg = failure_msg
        self.negater = negater

    def result(self) -> bool:
        """断言结果判断"""
        pass

    def custom_failure_msg(self):
        """错误信息"""
        pass

    def exec(self) -> Tuple[bool, Optional[str]]:
        if self.result() ^ self.negater:  # 异或
            return True, None
        else:
            if hasattr(self, 'custom_failure_msg'):
                return False, self.custom_failure_msg()
            else:
                return False, self.failure_msg


class AssertEqual(_Assert):
    def result(self):
        return self.expected == self.actual

    def custom_failure_msg(self):
        if not self.negater:
            return '实际值与期望值不相等: [期望值: %s][实际值: %s]' % (self.expected, self.actual)
        if self.negater:
            return '实际值与期望值相等: [期望值: %s][实际值: %s]' % (self.expected, self.actual)


class AssertSubString(_Assert):
    def result(self):
        return self.expected in self.actual

    def custom_failure_msg(self):
        if not self.negater:
            return '实际值未包含指定期望值: [期望值: %s][实际值: %s]' % (self.expected, self.actual)
        if self.negater:
            return '实际值包含指定期望值: [期望值: %s][实际值: %s]' % (self.expected, self.actual)


class AssertContains(_Assert):
    def result(self):
        res = re.findall(pattern=self.expected, string=self.actual)
        return len(res) > 0

    def custom_failure_msg(self):
        if not self.negater:
            return '实际值中未匹配到正则: [正则: %s][实际值: %s]' % (self.expected, self.actual)
        if self.negater:
            return '实际值中匹配到正则: [正则: %s][实际值: %s]' % (self.expected, self.actual)


class AssertMatches(_Assert):
    def result(self):
        res = re.fullmatch(pattern=self.expected, string=self.actual)
        return bool(res)

    def custom_failure_msg(self):
        if not self.negater:
            return '实际值完整未匹配到正则: [正则: %s][实际值: %s]' % (self.expected, self.actual)
        if self.negater:
            return '实际值完整匹配到正则: [正则: %s][实际值: %s]' % (self.expected, self.actual)


class AssertXPath(_Assert):

    xpath_separator = '|xpathSeparator|'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.xpath = self.expected.split(self.xpath_separator)[0]
        self.text = self.expected.split(self.xpath_separator)[-1]
        self.match_element_count = 0  # xpath匹配到元素数量

    def result(self):
        html = lxml.etree.HTML(self.actual)
        elements = html.xpath(self.xpath)
        if not isinstance(elements, Iterable):
            return False
        self.match_element_count = len(elements)
        if len(elements) > 1:
            return False
        elif len(elements) == 1:
            element = elements[0]
            if isinstance(element, str):
                return True if self.text == element else False
            else:
                return True if self.text == element.text else False
        else:
            return False

    def custom_failure_msg(self):
        if not self.negater:
            if self.match_element_count > 1:
                return '指定的XPath路径 %s 匹配到了 %s 个元素，请调整XPath表达式使其只匹配到一个被测元素' % (self.xpath, self.match_element_count)
            if self.match_element_count == 0:
                return '指定的XPath路径 %s 未匹配到任何元素' % self.xpath
            return '未在指定的XPath路径 %s 下找到文本内容 %s' % (self.xpath, self.text)
        if self.negater:
            return '在指定的XPath路径 %s 下找到文本内容 %s' % (self.xpath, self.text)
